package fun.qu_an.velocity.whitelist.api.whitelist;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.velocitypowered.api.util.UuidUtils;
import fun.qu_an.lib.basic.util.io.FileUtils;
import fun.qu_an.lib.basic.util.io.JsonUtils;
import fun.qu_an.lib.basic.util.result.Result;
import fun.qu_an.velocity.whitelist.util.UuidApiUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.net.SocketException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static fun.qu_an.velocity.whitelist.api.whitelist.Results.ILLEGAL_PLAYER_NAME;
import static fun.qu_an.velocity.whitelist.api.whitelist.Results.OFFLINE;

/**
 * 基于synchronizedBiMap和ConcurrentHashMap实现，增删改时锁定该对象
 */
public class ConcurrentWhitelist implements Whitelist {
	private final Path jsonPath;
	// 值：小写玩家名
	private final @NotNull Map<UUID, String> uuidMap;
	private final Map<String, UUID> inversedUuidMap;
	// 键：小写玩家名，值：原玩家名
	private final Map<String, String> nameMap = new ConcurrentHashMap<>();
	private final boolean online;

	protected ConcurrentWhitelist(@NotNull Path jsonPath, boolean online) {
		this.jsonPath = jsonPath;
		this.online = online;
		FileUtils.createFileAndDirs(jsonPath);
		BiMap<UUID, String> biMap = Maps.synchronizedBiMap(HashBiMap.create());
		uuidMap = biMap;
		inversedUuidMap = biMap.inverse();
	}

	@Override
	public boolean isOnline() {
		return online;
	}

	@Override
	public Result load() {
		try {
			JsonElement read = JsonUtils.read(jsonPath);
			if (read.isJsonArray()) {
				read.getAsJsonArray().forEach(jsonElement -> {
					if (!jsonElement.isJsonObject()) return;
					JsonObject jsonObject = jsonElement.getAsJsonObject();
					JsonElement uuid = jsonObject.get("uuid");
					JsonElement name = jsonObject.get("name");
					if (name == null || uuid == null) return;
					String uuidStr;
					String nameStr;
					try {
						uuidStr = uuid.getAsString();
						nameStr = name.getAsString();
					} catch (IllegalStateException e) {
						return;
					}
					if (uuidStr == null || nameStr == null) return;
					put(nameStr, UuidUtils.fromUndashed(uuidStr));
				});
			}
		} catch (IOException e) {
			return Result.exception(e);
		}
		return Result.success();
	}

	@Override
	public Result save() {
		List<Map<String, String>> list = new ArrayList<>(uuidMap.size());
		uuidMap.forEach((uuid, name) -> list.add(Map.of(
			"uuid", UuidUtils.toUndashed(uuid),
			"name", getCaseSensitiveName(name)
		)));
		try {
			JsonUtils.write(jsonPath, list);
		} catch (IOException e) {
			return Result.exception(e);
		}
		return Result.success();
	}

	@Override
	public Result add(String name) {
		if (!Whitelist.testName(name)) {
			return ILLEGAL_PLAYER_NAME;
		}
		JsonObject uuidJson;
		try {
			uuidJson = UuidApiUtils.getUuidJson(name, online);
		} catch (IOException e) {
			return Result.exception(e);
		}
		name = uuidJson.get("name").getAsString();
		UUID uuid = UuidUtils.fromUndashed(uuidJson.get("uuid").getAsString());
		return add(name, uuid);
	}

	@Override
	@Deprecated
	public Result add(@NotNull UUID uuid) {
		if (!online) return OFFLINE;
		JsonObject json;
		try {
			json = UuidApiUtils.getProfileJson(uuid, true);
		} catch (IOException e) {
			return Result.exception(e);
		}
		return add(json.get("name").getAsString(), uuid);
	}

	@Override
	public Result remove(@NotNull String name) {
		if (!Whitelist.testName(name)) {
			return ILLEGAL_PLAYER_NAME;
		}
		String lowerCaseName = name.toLowerCase(Locale.ROOT);
		if (!inversedUuidMap.containsKey(lowerCaseName)) {
			return Result.ineffective();
		}
		synchronized (this) {
			UUID uuid = inversedUuidMap.remove(lowerCaseName);
			if (uuid == null) {
				return Result.ineffective();
			}
			String returnValue = nameMap.remove(lowerCaseName);
			return Result.success(returnValue);
		}
	}

	@Override
	public Result remove(UUID uuid) {
		if (!uuidMap.containsKey(uuid)) {
			return Result.ineffective();
		}
		synchronized (this) {
			String lowerCaseName = uuidMap.remove(uuid);
			if (lowerCaseName == null) {
				return Result.ineffective();
			}
			String returnValue = nameMap.remove(lowerCaseName);
			return Result.success(returnValue);
		}
	}

	@Override
	public List<String> list() {
		return List.copyOf(nameMap.values());
	}

	@Override
	public @Nullable UUID getUuidByName(@NotNull String name) {
		if (!Whitelist.testName(name)) {
			return null;
		}
		return inversedUuidMap.get(name.toLowerCase(Locale.ROOT));
	}

	@Override
	public @Nullable String getNameByUuid(UUID uuid) {
		String name = uuidMap.get(uuid);
		if (name == null) return null;
		return getCaseSensitiveName(name);
	}

	@Override
	public boolean isInWhitelist(UUID uuid) {
		return uuidMap.containsKey(uuid);
	}

	@Override
	public Result updatePlayer(@NotNull UUID uuid, @NotNull String name) {
		if (!Whitelist.testName(name)) {
			return ILLEGAL_PLAYER_NAME;
		}
		if (online) { // 在线模式根据 UUID 更新玩家名
			if (!isInWhitelist(uuid)) {
				return Result.failure(); // 不在白名单
			}
			if (name.equals(getNameByUuid(uuid))) {
				return Result.ineffective(); // 玩家名无变化
			}
			// 获取该UUID的新玩家名
			JsonObject profileJson;
			try {
				profileJson = UuidApiUtils.getProfileJson(uuid, true);
			} catch (IOException e) {
				return Result.ineffective().subResult(Result.exception(e)); // 查询失败
			}
			// 更新白名单
			put(profileJson.get("name").getAsString(), uuid);
		} else { // 离线模式根据玩家名更新 UUID
			UUID oldUuid = getUuidByName(name);
			if (oldUuid == null) {
				return Result.failure(); // 不在白名单
			}
			if (oldUuid.equals(UuidUtils.generateOfflinePlayerUuid(name))) {
				return Result.ineffective(); // UUID无变化
			}
			synchronized (this) {
				// 更新白名单
				remove(oldUuid);
				put(name, uuid);
			}
		}
		return Result.success("{\"name\":\"" + name + "\",\"uuid\":\"" + UuidUtils.toUndashed(uuid) + "\"}");
	}

	private Result add(@NotNull String name, @NotNull UUID uuid) {
		String returnValue;
		if (online) { // 在线模式以 UUID 为准
			if (isInWhitelist(uuid)) {
				return Result.ineffective(name); // 玩家已存在
			}
			// 检查是否有原有的玩家的旧玩家名和正在添加的新玩家一致
			// 即：新添加的B玩家使用了A玩家使用过的旧名字
			UUID uuidA = getUuidByName(name);
			if (uuidA != null) {
				if (uuid.equals(uuidA)) { // A和B是同一玩家
					return Result.ineffective(name); // 玩家已存在
				}
				// 新添加的B玩家使用了A玩家使用过的旧名字
				// 即白名单中A玩家的玩家名已过时，需要更新
				JsonObject profileJson = null;
				try {
					// 获取该UUID的新玩家名
					profileJson = UuidApiUtils.getProfileJson(uuidA, true);
				} catch (SocketException e) {
					return Result.exception(e); // 查询失败
				} catch (IOException ignored) {
				}
				if (profileJson != null) { // 如果在线模式并且获取成功，更新白名单
					put(profileJson.get("name").getAsString(),
						UuidUtils.fromUndashed(profileJson.get("uuid").getAsString()));
				}
			}
			returnValue = "{\"name\":" + name + "\",\"uuid\":\"" + UuidUtils.toUndashed(uuid) + "\"}";
		} else { // 离线模式以玩家名为准
			if (getUuidByName(name) != null) {
				return Result.ineffective(name); // 玩家已存在
			}
			returnValue = name;
		}
		put(name, uuid);
		return Result.success(returnValue);
	}

	private void put(@NotNull String name, UUID uuid) {
		String lowerCaseName = name.toLowerCase(Locale.ROOT);
		synchronized (this) {
			uuidMap.put(uuid, lowerCaseName);
			nameMap.put(lowerCaseName, name);
		}
	}

	private String getCaseSensitiveName(@NotNull String name) {
		return nameMap.get(name.toLowerCase(Locale.ROOT));
	}
}
